package bancosys.tec.persist.persister;

import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;

import jmine.tec.persist.PersistMessages;
import jmine.tec.utils.debug.TimerGroup;
import jmine.tec.utils.debug.impl.DebugRuntimeFactory;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.audit.AuditManager;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.persist.persister.listener.PersisterListener;
import bancosys.tec.persist.validator.Validator;
import bancosys.tec.persist.validator.ValidatorFactory;

/**
 * @created Jul 11, 2007
 * @author Gustavo Almeida
 * @param <T> classe do bean
 */
public abstract class AbstractPersister<T extends Persistable> implements Persister<T> {

    public static final String PERSISTENCE_TIMER = "Persistence Timer";

    private T target = null;

    private SessionFactory sessionFactory = null;

    private ValidatorFactory validatorFactory = null;

    private AuditManager auditManager = null;

    private Set<PersisterListener<T>> listeners = new LinkedHashSet<PersisterListener<T>>();

    private Set<Validator<T>> validators = new LinkedHashSet<Validator<T>>();

    /**
     * Devolve a fábrica de sessões do hibernate.
     * 
     * @return a fábrica de sessões do hibernate.
     */
    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    /**
     * Define a fábrica de sessões do hibernate.
     * 
     * @param sessionFactory a fábrica de sessões do hibernate.
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Define a fábrica de validadores da persistência.
     * 
     * @param validatorFactory fábrica de validadores.
     */
    public void setValidatorFactory(ValidatorFactory validatorFactory) {
        this.validatorFactory = validatorFactory;
    }

    /**
     * Define o gerenciador de auditoria.
     * 
     * @param auditManager o gerenciador de auditoria.
     */
    public void setAuditManager(AuditManager auditManager) {
        this.auditManager = auditManager;
    }

    /**
     * Devolve o validador para o <code>target</code> atual.
     * 
     * @return o validador para o <code>target</code> atual.
     */
    public Validator<T> getValidator() {
        if (this.getTarget() == null) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_GET_VALIDATOR_FOR_NULL_TARGET.create());
        }
        return this.validatorFactory.getValidator(this.getTarget());
    }

    /**
     * Define o objeto que será persistido por esse persister.
     * 
     * @param bean o objeto que será persistido por esse persister.
     */
    public void setTarget(T bean) {
        this.target = bean;
    }

    /**
     * Devolve a session do hibernate.
     * 
     * @return a session do hibernate.
     */
    protected Session getSession() {
        return this.getSessionFactory().getCurrentSession();
    }

    /**
     * Devolve o objeto que será persistido por esse persister.
     * 
     * @return o objeto que será persistido por esse persister.
     */
    protected T getTarget() {
        return this.target;
    }

    /**
     * Devolve o gerenciador de auditoria.
     * 
     * @return o gerenciador de auditoria.
     */
    public AuditManager getAuditManager() {
        return this.auditManager;
    }

    /**
     * Faz a auditoria da instância.
     * 
     * @param instance a instância a ser auditada.
     * @param action a ação usada.
     * @param id {@link Serializable} com o id que refencia a mesma instancia na outra base
     */
    protected void audit(Persistable instance, PersistenceAction action, Serializable id) {
        this.getAuditManager().audit(instance, action, id);
    }

    /**
     * Identifica se a operação é uma inserção ou atualização. Método trazido do <code>AuthPersiterAdapter</code>. Esse método pode não
     * funcionar corretamente.
     * 
     * @return <code>PersistenceAction.insert</code> se a operação for insert, <code>PersistenceAction.update</code> caso contrário.
     * @see AuthPersisterAdapter
     * @see AuthPersisterAdapter#getCommandType
     * @see PersistenceAction
     */
    protected final PersistenceAction insertOrUpdate() {
        Serializable pk = this.getTarget().getPk();
        return pk == null ? PersistenceAction.insert : PersistenceAction.update;
    }

    /**
     * Adiciona uma lista de listener para este persister executar processos de outros componentes.
     * 
     * @param persisterListenerList Lista de Listener a ser adicionado ao persister.
     */
    public void setListeners(Set<PersisterListener<T>> persisterListenerList) {
        this.listeners.addAll(persisterListenerList);
    }

    /**
     * Adiciona uma lista de validators para este persister.
     * 
     * @param validatorList lista de validators a serem adicionados.
     */
    public void setValidators(Set<Validator<T>> validatorList) {
        this.validators.addAll(validatorList);
    }

    /**
     * Retorna a lista de listener configurados para este persister.
     * 
     * @return Liste de listener.
     */
    public Set<PersisterListener<T>> getListeners() {
        return this.listeners;
    }

    /**
     * Executado após o insert na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a este
     * método.
     * 
     * @param bean bean.
     * @param action action
     */
    protected final void fireAfterInsert(T bean, Object action) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".afterInsert";
            try {
                getPersistenceTimer().start(identifier);
                listener.afterInsert(bean, action);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executado antes do insert na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a
     * este método.
     * 
     * @param bean bean.
     * @param action action
     */
    protected final void fireBeforeInsert(T bean, Object action) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".beforeInsert";
            try {
                getPersistenceTimer().start(identifier);
                listener.beforeInsert(bean, action);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executado após o update na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a este
     * método.
     * 
     * @param bean bean.
     * @param action action
     */
    protected final void fireAfterUpdate(T bean, Object action) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".afterUpdate";
            try {
                getPersistenceTimer().start(identifier);
                listener.afterUpdate(bean, action);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executado antes do update na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a
     * este método.
     * 
     * @param bean bean.
     * @param action action
     */
    protected final void fireBeforeUpdate(T bean, Object action) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".beforeUpdate";
            try {
                getPersistenceTimer().start(identifier);
                listener.beforeUpdate(bean, action);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executado após o remove na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a este
     * método.
     * 
     * @param bean bean.
     */
    protected final void fireAfterRemove(T bean) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".afterRemove";
            try {
                getPersistenceTimer().start(identifier);
                listener.afterRemove(bean);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executado antes do remove na base quente. Percorre a lista de listeners configurado a este persister e dispara o evento coerente a
     * este método.
     * 
     * @param bean bean.
     */
    protected final void fireBeforeRemove(T bean) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".beforeRemove";
            try {
                getPersistenceTimer().start(identifier);
                listener.beforeRemove(bean);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * Executa a exclusao de dependencias de todos os listeners registrados a entidade a ser excluida.
     * 
     * @param bean bean.
     */
    protected final void fireRemoveDependencies(T bean) {
        for (PersisterListener<T> listener : this.getListeners()) {
            String identifier = listener.getClass().getName() + ".removeDependencies";
            try {
                getPersistenceTimer().start(identifier);
                listener.doRemoveDependencies(bean);
            } finally {
                getPersistenceTimer().stop(identifier);
            }
        }
    }

    /**
     * @return the {@link TimerGroup} to be used on persistence operations
     */
    public static TimerGroup getPersistenceTimer() {
        return DebugRuntimeFactory.getInstance().getOrCreateTimer(PERSISTENCE_TIMER);
    }
}
